use std::sync::Arc;

use tauri::Window;
use tokio::{
    io::{AsyncReadExt, AsyncWriteExt},
    sync::Semaphore,
};
use tokio_serial::{SerialPortBuilderExt, SerialStream};

use crate::{
    error::{new_proto_err, new_wrap_err, AppErr, AppErrWrap},
    utils::{get_mut, new_bytes, xor_sum},
};

const BAUD_RATE: u32 = 115200;

unsafe impl Send for SerialPortConn {}
unsafe impl Sync for SerialPortConn {}

pub struct SerialPortConn {
    stream: SerialStream,
    exit_sem: Semaphore,
    app: Window,
}

impl SerialPortConn {
    pub fn new(name: &str, app: Window) -> Result<Arc<SerialPortConn>, AppErr> {
        let stream = tokio_serial::new(name, BAUD_RATE)
            .open_native_async()
            .wrap_err()?;
        let conn = SerialPortConn {
            stream,
            exit_sem: Semaphore::new(0),
            app,
        };

        let conn = Arc::new(conn);
        tokio::spawn(SerialPortConn::read_loop(conn.clone()));
        Ok(conn)
    }

    pub async fn write(&self, buf: &[u8]) -> Result<(), AppErr> {
        let stream = get_mut(&self.stream);
        stream.write_all(buf).await?;
        Ok(())
    }

    async fn read(&self) -> Result<Box<[u8]>, AppErr> {
        let stream = get_mut(&self.stream);
        let len = sync(stream).await? - 3;
        let mut buf = new_bytes(len);
        stream.read_exact(&mut buf).await?;

        let sum = xor_sum(&buf[..(len - 2)]);
        if sum != buf[len - 2] {
            return new_proto_err("check sum invalid".to_string());
        }
        if END != buf[len - 1] {
            return new_proto_err("end invalid".to_string());
        }

        Ok(buf)
    }

    pub fn close(&self) {
        self.exit_sem.add_permits(1);
    }

    async fn read_loop(conn: Arc<SerialPortConn>) {
        loop {
            let buf = tokio::select! {
                buf = conn.read() => {
                    buf
                }
                _ = conn.exit_sem.acquire() => {
                    println!("closed");
                    break;
                }
            };
            let buf = match buf {
                Ok(v) => v,
                Err(AppErr::Wrap(e)) => {
                    println!("{}", e);
                    break;
                }
                Err(e) => {
                    println!("{}", e);
                    continue;
                }
            };
            _ = conn.app.emit(SERIAL_RECV_EVENT, buf);
        }
    }
}

const SERIAL_RECV_EVENT: &'static str = "SERIAL_RECV_EVENT";

const HEAD0: u8 = 0xE1;
const HEAD1: u8 = 0x1E;
const END: u8 = 0xEF;

async fn sync(stream: &mut SerialStream) -> Result<usize, AppErr> {
    let mut buf: [u8; 1] = [0; 1];
    let mut flag = false;
    loop {
        let n = stream.read(&mut buf).await?;
        if n < 1 {
            return new_wrap_err("serial port closed".to_string());
        }
        if flag && (buf[0] == HEAD1) {
            break;
        }
        flag = buf[0] == HEAD0;
    }
    let n = stream.read(&mut buf).await?;
    if n < 1 {
        return new_wrap_err("serial port closed".to_string());
    }

    let len = buf[0] as usize;
    if len < 7 {
        return new_proto_err("proto len < 7".to_string());
    }

    Ok(len)
}
